#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

#define FB_DEVICE_NAME "/dev/fb0"
int fd_fb;
struct fb_var_screeninfo var;	/* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;


//14byte文件头
typedef struct
{
    char    cfType[2];//文件类型，"BM"(0x4D42)
    int     cfSize;//文件大小（字节）
    int cfReserved;//保留，值为0
    int     cfoffBits;//数据区相对于文件头的偏移量（字节）
}__attribute__((packed)) BITMAPFILEHEADER;
//__attribute__((packed))的作用是告诉编译器取消结构在编译过程中的优化对齐

//40byte信息头
typedef struct
{
    char ciSize[4];//BITMAPFILEHEADER所占的字节数
    int  ciWidth;//宽度
    int  ciHeight;//高度
    char ciPlanes[2];//目标设备的位平面数，值为1
    int  ciBitCount;//每个像素的位数
    char ciCompress[4];//压缩说明
    char ciSizeImage[4];//用字节表示的图像大小，该数据必须是4的倍数
    char ciXPelsPerMeter[4];//目标设备的水平像素数/米
    char ciYPelsPerMeter[4];//目标设备的垂直像素数/米
    char ciClrUsed[4]; //位图使用调色板的颜色数
    char ciClrImportant[4]; //指定重要的颜色数，当该域的值等于颜色数时（或者等于0时），表示所有颜色都一样重要
}__attribute__((packed)) BITMAPINFOHEADER;

typedef struct PictureData {
	int picture_Fd;//文件句柄
	int iBpp;
	int iRgbSize;
	
	int iWidth;
	int iHeight;
	unsigned char *pucRgbData;
	/* use for BMP */
	unsigned char *pucFileData;//整个bmp文件的数据	
	unsigned char *pucBmpData;//bmp图像的数据
	int iFileSize;//BMP文件大小
	int iBmpDataSize;//BMP原始数据rgba大小
}T_PictureData, *PT_PictureData;

/**********************************************************************
 * 函数名称： IsBmp
 * 功能描述： 判断该文件是否为BMP文件
 * 输入参数： ptFileMap - 内含文件信息
 * 输出参数： 无
 * 返 回 值： >0 - 是BMP格式, -1 -不是BMP格式
 ***********************************************************************/
int IsBmp(const char *strFileName) 
{
	char strCheckHeader[2];
    int pt_fd;
	pt_fd= open(strFileName, O_RDWR);
	if (pt_fd < 0)
    {
		return -1;
	}
	if (read(pt_fd, strCheckHeader, 2) != 2) 
		return -1;
    
	if (strCheckHeader[0] != 0x42 || strCheckHeader[1] != 0x4d)
		return -1;
	else
		return pt_fd;
}

/**********************************************************************
 * 函数名称： MapFile
 * 功能描述： 使用mmap函数映射一个文件到内存,以后就可以直接通过内存来访问文件
 * 输入参数： PT_PictureData ptData 内含图像数据
 * 输出参数： ptData->iFileSize     : 文件大小
 *                        ptData->pucFileData : 映射内存的首地址
 * 返 回 值： 0      - 成功其他值 - 失败
 ***********************************************************************/
int MapFile(PT_PictureData ptData)
{
	struct stat tStat;

	fstat(ptData->picture_Fd, &tStat);
	ptData->iFileSize= tStat.st_size;
	ptData->pucFileData= (unsigned char *)mmap(NULL , tStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, ptData->picture_Fd, 0);
	if (ptData->pucFileData == (unsigned char *)-1)
	{
		printf("mmap error!\n");
		return -1;
	}
	return 0;
}

/**********************************************************************
 * 函数名称： DecodeBmp2Rgb
 * 功能描述：把BMP文件转化为rgb格式
 * 输入参数： strFileName - 文件名
 * 					 ptData - 内含图像信息
 * 返 回 值： 0      - 成功其他值 - 失败
 *                         -1     - 文件不是BMP格式
 *                         -2     - 不支持的bpp
 *                         -3     - 图像缓存区分配失败
 ***********************************************************************/
static int DecodeBmp2Rgb(const char *strFileName, PT_PictureData ptData) {
	int x,y;
	int iPos = 0;
	int iLineWidthAlign;
	BITMAPFILEHEADER *ptBITMAPFILEHEADER;
	BITMAPINFOHEADER *ptBITMAPINFOHEADER;
	unsigned char *aFileHead;
	unsigned char *pucSrc;
	unsigned char *pucDest;
	int iLineBytes;

	/* 判断该文件是否为BMP格式 */
    ptData->picture_Fd = IsBmp(strFileName);
	if (ptData->picture_Fd < 0)
		return -1;

	/* 将BMP文件映射到内存空间 */	
	MapFile(ptData);


	aFileHead = ptData->pucFileData;

	ptBITMAPFILEHEADER = (BITMAPFILEHEADER *)aFileHead;
	ptBITMAPINFOHEADER = (BITMAPINFOHEADER *)(aFileHead + sizeof(BITMAPFILEHEADER));
	/* 获取必要的图像信息 */
	ptData->iWidth = ptBITMAPINFOHEADER->ciWidth;
	ptData->iHeight = ptBITMAPINFOHEADER->ciHeight;
	ptData->iBpp = ptBITMAPINFOHEADER->ciBitCount;
    iLineBytes = ptData->iWidth*ptData->iBpp/8;//一行数据的字节数
	ptData->iBmpDataSize= ptData->iHeight * iLineBytes;//整个BMP图像的字节数
	/*暂时只支持24bpp格式*/
	if (ptData->iBpp != 24)
	{
		printf("iBMPBpp = %d\n", ptData->iBpp);
		printf("sizeof(BITMAPFILEHEADER) = %d\n", sizeof(BITMAPFILEHEADER));
		return -2;
	}

	/* 分配空间 */
	ptData->pucBmpData = malloc(ptData->iBmpDataSize);
	ptData->pucRgbData = malloc(ptData->iBmpDataSize);
	
	if (NULL == ptData->pucBmpData||NULL == ptData->pucRgbData)
		return -2;

	/* 从bmp文件中读取图像信息，24bpp的BMP图像为BGR格式 */
	pucDest = ptData->pucBmpData;
	iLineWidthAlign = (iLineBytes + 3) & ~0x3;   /* 向4取整 */
	pucSrc = aFileHead + ptBITMAPFILEHEADER->cfoffBits;

	pucSrc = pucSrc + (ptData->iHeight - 1) * iLineWidthAlign;

	/* 对于bmp文件中的源数据，是以左下角为原点计算坐标的，因此拷贝数据时需要转换坐标 */
	for (y = 0; y < ptData->iHeight; y++)
	{		
		memcpy(pucDest, pucSrc, ptData->iWidth*3);
		pucSrc  -= iLineWidthAlign;
		pucDest += iLineBytes;
	}

	
	/* 将得到的BGR数据转化为RGB数据 */
	for (y = 0; y < ptData->iHeight; y++){		
		for(x = 0;x<ptData->iWidth*3;x+=3){
			ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+2];
			ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+1];
			ptData->pucRgbData[iPos++] = ptData->pucBmpData[y*ptData->iWidth*3+x+0];
		}
	}
	
	return 0;
}



/**********************************************************************
 * 函数名称： ReleaseBmp2Rgb
 * 功能描述：释放解析及使用BMP时的空间
 * 输入参数：ptData - 内含图像信息
 * 返 回 值： 无
 ***********************************************************************/
static void ReleaseBmp2Rgb(PT_PictureData ptData)
{
	free(ptData->pucRgbData);
	free(ptData->pucBmpData);
	if(munmap(ptData->pucFileData,ptData->iFileSize)!=0)
	printf("munmap fault\n");
    close(ptData->picture_Fd);
}

/**********************************************************************
 * 函数名称： FBDeviceInit
 * 功能描述： "framebuffer显示设备"的初始化函数
 * 输入参数： 无
 * 输出参数： 无
 * 返 回 值： 0 - 成功, 其他值 - 失败
 ***********************************************************************/
static int FBDeviceInit(void)
{
	int ret;
	
	fd_fb = open(FB_DEVICE_NAME, O_RDWR);
	if (0 > fd_fb)
	{
		printf("can't open %s\n", FB_DEVICE_NAME);
	}

	ret = ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
	if (ret < 0)
	{
		printf("can't get fb's var\n");
		return -1;
	}

	screen_size = var.xres * var.yres * var.bits_per_pixel / 8;  //整屏全部像素点所占的字节数：800*480*32/8=1536000Byte = 1500KB ≈ 1.46MB
	fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
	if (fbmem == (unsigned char *)-1)
	{
		printf("can't mmap\n");
		return -1;
	}

    line_width  = var.xres * var.bits_per_pixel / 8;  //每一行像素点所占据的字节数 800*32/8bit = 
	pixel_width = var.bits_per_pixel / 8;  //一个像素点的宽度 32/8bit = 4Byte
	
	return 0;
}

/**********************************************************************
 * 函数名称： lcd_put_pixel
 * 功能描述： 在LCD指定位置上输出指定颜色（描点）
 * 输入参数： x坐标，y坐标，颜色
 * 输出参数： 无
 * 返 回 值： 会
 ***********************************************************************/ 
void lcd_put_pixel(int x, int y, unsigned int color)
{
	unsigned int *pen_32 = (unsigned int *)(fbmem+y*line_width+x*pixel_width);
	*pen_32 = color;
}

/**********************************************************************
 * 函数名称： FbShowPicture
 * 功能描述：将转换后的rgb数据显示到LCD上
 * 输入参数： ptData - 内含图像信息
 * 返 回 值： 无
 ***********************************************************************/
static void FbShowPicture(PT_PictureData ptData){
	int i ,j;
	int iColor;
	for(i = 0; i<ptData->iHeight; i++){
		for(j = 0; j<ptData->iWidth*3; j+=3){
			if(j/3<var.xres && i<var.yres){
				iColor = (ptData->pucRgbData[i*ptData->iWidth*3+j+0]<<16) + (ptData->pucRgbData[i*ptData->iWidth*3+j+1]<<8) + (ptData->pucRgbData[i*ptData->iWidth*3+j+2]<<0);
				lcd_put_pixel(j/3, i, iColor);
			}
		}
	}
}

int main(int argc,char *argv[])
{
    T_PictureData ptData;
    int ret;

    ret = FBDeviceInit();
    printf("ret = %d\n", ret);

    ret = DecodeBmp2Rgb(argv[1], &ptData);
    printf("ret = %d\n", ret);

    FbShowPicture(&ptData);

    ReleaseBmp2Rgb(&ptData);

    return 0;
}